﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using Xceed.Silverlight.Data;
using Xceed.Silverlight.ListBox.PositioningFunction;
using Xceed.Silverlight.ListBox.Utils;
using Xceed.Silverlight.Utils;

namespace Xceed.Silverlight.ListBox
{
  public abstract partial class PanelBase : Panel, IScrollInfo, ISupportInitialize, IWeakEventListener
  {
#if WINDOWS_PHONE
    // Deceleration rate of inertia animations (in pixels / miliseconds squared)
    private const double InertiaDeceleration = 0.001d;
#endif

    #region Constructor

    internal PanelBase()
    {
      this.ScrollManager = new ScrollManager( this );
      this.ScrollManager.ActualScrollIndexChanged += this.OnScrollManagerActualScrollIndexChanged;
      this.ScrollManager.ScrollCompleted += this.OnScrollManagerScrollCompleted;

      this.EnumeratorHelper = new EnumeratorHelper();
      this.EnumeratorHelper.MoveCompleted += this.OnEnumeratorMoveCompleted;

      this.LayoutManager = new PanelLayoutManager( this );
      this.SelectionAndFocusManager = new PanelSelectionAndFocusManager( this );

#if WINDOWS_PHONE
      this.Loaded += new RoutedEventHandler( this.OnPanelLoaded );
#endif
    }

    #endregion

    #region ParentListBox Property

    internal ListBox ParentListBox
    {
      get
      {
        return this.GetValue( ListBox.ParentListBoxProperty ) as ListBox;
      }
    }

    internal virtual void OnParentListBoxChanged( PanelBase panel, DependencyPropertyChangedEventArgs e )
    {
    }

    #endregion

    #region PositioningFunction Property

    internal virtual PositioningFunctionBase PositioningFunction
    {
      get
      {
        return m_positioningFunction;
      }
      set
      {
        if( m_positioningFunction != value )
        {
          m_positioningFunction = value;

          if( this.IsInitialized )
          {
            this.LayoutManager.LaunchViewChangedTransition();
          }

        }
      }
    }

    #endregion

    #region InjectedGroupHeaderCount Private Property

    private int InjectedGroupHeaderCount
    {
      get;
      set;
    }

    #endregion

    #region MeasureOverrideInhibited Internal Property

    internal bool MeasureOverrideInhibited
    {
      get;
      set;
    }

    #endregion

    #region ArrangeOverrideInhibited Internal Property

    internal bool ArrangeOverrideInhibited
    {
      get;
      set;
    }

    #endregion

    #region ScrollManager Internal Property

    internal ScrollManager ScrollManager
    {
      get;
      private set;
    }

    #endregion

    #region LastNavigationKey Internal Property

    internal Key LastNavigationKey
    {
      get;
      set;
    }

    #endregion

    #region EnumeratorHelper Internal Property

    internal EnumeratorHelper EnumeratorHelper
    {
      get;
      private set;
    }

    #endregion

    #region BringLastItemToStartingPoint Property

    internal virtual bool BringLastItemToStartingPoint
    {
      get
      {
        return false;
      }
    }

    #endregion

    #region ItemOffset Property

    internal virtual double ItemOffset
    {
      get
      {
        return this.VerticalOffset;
      }
    }

    #endregion

    #region ItemsInViewport Property

    internal virtual double ItemsInViewport
    {
      get
      {
        return this.LayoutManager.ContainerDescriptions.Count;
      }
    }

    #endregion

    #region IsInitialized Property

    internal bool IsInitialized
    {
      get;
      private set;
    }

    #endregion

    #region ActualContainerSize Property

    internal Size ActualContainerSize
    {
      get
      {
        return m_actualContainerSize;
      }
      set
      {
        if( value != m_actualContainerSize )
        {
          m_actualContainerSize = value;
        }
      }
    }

    #endregion

    #region ViewportSizeInPixels Property

    internal Size ViewportSizeInPixels
    {
      get
      {
        return m_viewportSizeInPixels;
      }
      set
      {
        if( m_viewportSizeInPixels != value )
        {
          m_viewportSizeInPixels = value;
          this.UpdatePositionningFunction();
        }
      }
    }

    #endregion

    #region AllowStickyHeaders Internal Read-Only Property

    internal virtual bool AllowStickyHeaders
    {
      get
      {
        return true;
      }
    }

    #endregion

    #region Mesure/Arrange

    protected override Size MeasureOverride( Size availableSize )
    {
      bool isInDesign = ( Xceed.Silverlight.Compatibility.CompatibilityUtils.IsInDesignTool )
                     || ( Xceed.Utils.Licensing.DesignerProperties.GetIsInDesignMode() );

      if( double.IsInfinity( availableSize.Width ) || double.IsNaN( availableSize.Width ) )
      {
        if( !isInDesign )
          throw new InvalidOperationException( "An attempt was made to use the ListBox in an unconstrained environment (e.g., StackPanel without set values for the Width and Height properties)." );

        //Temporary value for design time purpose.
        availableSize.Width = 640d;
      }

      if( double.IsInfinity( availableSize.Height ) || double.IsNaN( availableSize.Height ) )
      {
        if( !isInDesign )
          throw new InvalidOperationException( "An attempt was made to use the ListBox in an unconstrained environment (e.g., StackPanel without set values for the Width and Height properties)." );

        //Temporary value for design time purpose.
        availableSize.Height = 480d;
      }

      if( this.LayoutManager.IsLayoutInhibited )
      {
        this.MeasureOverrideInhibited = true;
        return availableSize;
      }

      foreach( ListBoxContainer container in this.Children )
      {
        container.Measure( availableSize );

        // Ensure to update the CachedSize for each container
        // after a measure pass.
        if( container.Configuration != null )
        {
          if( container.Configuration.UpdateCachedSize( container ) )
            m_forceRefresh = true;
        }
      }

      // We must update the ViewportSizeInPixel before calling 
      // UpdateIScrollInfoPropertiesFromMeasureOverrideAvailableSize since 
      // it is used by some overrides of InvalidateViewportSize() and 
      // it could determine if InvalidateScrollInfo is required or not
      // e.g.: when the ListBox is resized
      Size currentAvailableSize = this.ViewportSizeInPixels;

      this.ViewportSizeInPixels = availableSize;

      bool invalidateScrollInfoRequired = 
        PanelBase.ShouldUpdateIScrollInfoFromMeasureOverrideHelper( this, availableSize, currentAvailableSize, this.GetScrollingOrientation() );

      if( invalidateScrollInfoRequired || m_forceRefresh )
      {
        if( !this.ScrollManager.IsScrollManagerActive )
        {
          this.InvalidateScrollInfo();
        }

        this.LayoutManager.UpdateViewportContent( availableSize );
      }

      m_forceRefresh = false;

      return availableSize;
    }

    protected override Size ArrangeOverride( Size finalSize )
    {
      if( this.LayoutManager.IsLayoutInhibited )
      {
        this.ArrangeOverrideInhibited = true;
        return finalSize;
      }

      foreach( ListBoxContainer container in this.Children )
      {
        container.Arrange( new Rect( new Point( 0, 0 ), new Size( container.DesiredSize.Width, container.DesiredSize.Height ) ) );
      }

      // Dirty way to clip the outer content
      RectangleGeometry clip = this.Clip as RectangleGeometry;

      if( clip == null )
        clip = new RectangleGeometry();

      clip.Rect = new Rect( PanelBase.EmptyPoint, finalSize );
      this.Clip = clip;

      return finalSize;
    }

    #endregion

    #region Viewport Content

    internal void ComputeEstimatedSizeForConfig( DataPath datapath, Size viewport )
    {
      ListBox listBox = this.ParentListBox;
      ListBoxContainerConfigurationBase itemConfiguration = listBox.GetContainerFactory( ConfigurationType.Item );
      ListBoxContainerConfigurationBase groupHeaderConfiguration = listBox.GetContainerFactory( ConfigurationType.GroupHeader );
      ListBoxContainerConfigurationBase currentConfiguration;
      ListBoxContainer currentReferenceContainer;

      bool isGroupDataItem = ( datapath.LastChild is GroupDataItem );
      bool foundInDictionnary = false;

      if( isGroupDataItem )
      {
        currentConfiguration = groupHeaderConfiguration;
        currentReferenceContainer = m_referenceContainerForGroup;
      }
      else
      {
        currentConfiguration = itemConfiguration;
        currentReferenceContainer = m_referenceContainerForItem;
      }

      if( currentReferenceContainer == null )
      {
        // Find container for the dataPath
        if( this.LayoutManager.DataPathToContainerMapping.TryGetValue( datapath, out currentReferenceContainer ) )
        {
          foundInDictionnary = true;
        }

        if( currentReferenceContainer == null )
        {
          currentReferenceContainer = this.GetAvailableContainer( currentConfiguration, 0, datapath );
        }

        // Prepare the container so it's dataTemplate is assigned
        listBox.PrepareContainerForItem( currentReferenceContainer, datapath );

        // Mesure the container so we get a rough estimated size
        currentReferenceContainer.Measure( viewport );
        currentConfiguration.UpdateCachedSize( currentReferenceContainer );

        // Recycle the container if we didn't picked it from the dictionnary
        if( !foundInDictionnary )
        {
          // Even though we do not explicitly want to defer container recycling
          // the container will be put in the waiting list if it is not elligible
          // for recycling.
          currentReferenceContainer.RecycleEvenIfFocused = true;
          this.RecycleContainerAndLayoutOutOfView( currentReferenceContainer );
        }
      }

      if( isGroupDataItem )
      {
        m_referenceContainerForGroup = currentReferenceContainer;
      }
      else
      {
        m_referenceContainerForItem = currentReferenceContainer;
      }
    }

    internal virtual double GetAverageContainerSize()
    {
      ListBoxContainerConfigurationBase itemConfiguration = this.ParentListBox.GetContainerFactory( ConfigurationType.Item );
      ListBoxContainerConfigurationBase groupHeaderConfiguration = this.ParentListBox.GetContainerFactory( ConfigurationType.GroupHeader );

      Size itemSize = new Size();
      Size groupSize = new Size();

      if( itemConfiguration != null )
        itemSize = itemConfiguration.CachedSize;

      if( groupHeaderConfiguration != null )
        groupSize = groupHeaderConfiguration.CachedSize;

      return ( itemSize.Height + groupSize.Height ) / 2;
    }

    internal ListBoxContainer GetAvailableContainer( 
      ListBoxContainerConfigurationBase configuration, 
      int zOrder,
      DataPath dataPath )
    {
      ListBoxContainer container = this.DequeueRecycledContainer( configuration, dataPath );

      if( container == null )
      {
        container = this.CreateNewContainer( configuration, zOrder );
      }

      return container;
    }

    internal virtual void AdjustContainerTargetPosition( ListBoxContainer container )
    {
    }

    private ListBoxContainerConfigurationBase GetContainerConfigurationFromDataPath( DataPath dataPath,
                                                                                     ListBoxContainerConfigurationBase itemConfiguration,
                                                                                     ListBoxContainerConfigurationBase groupHeaderConfiguration )
    {
      if( dataPath == null )
        throw new ArgumentNullException( "DataPath shouldn't be null here.", "dataPath" );

      if( dataPath.LastChild is GroupDataItem )
      {
        return groupHeaderConfiguration;
      }
      else
      {
        return itemConfiguration;
      }

    }

    private void InvalidateContainerPreparation()
    {
      foreach( ListBoxContainer container in this.LayoutManager.GetLayoutedContainers( false ) )
      {
        container.InvalidatePrepare();
      }
    }

    private ListBoxContainer CreateNewContainer( ListBoxContainerConfigurationBase configuration, int zOrder )
    {
      ListBoxContainer container = configuration.CreateContainer();

      // If there's an animation running, add the container to the animation
      this.LayoutManager.AddNewContainerToCurrentTransitions( container );

      this.AddContainerToPanel( container, zOrder );

      return container;
    }

    private static int CompareDataPaths( DataPath firstDataPath, DataPath secondDataPath )
    {
      //This method will return 0 if the two node paths are equivalent 
      //This method will return a negative number that indicates how much the first node path needs to be unwinded to get to a common root with the second node path
      //This method will return a positive number if the second node path is longer (and root are equivalents).

      if( firstDataPath == null || firstDataPath.Depth == 0 )
        return 0;

      if( secondDataPath == null || secondDataPath.Depth == 0 )
        return -firstDataPath.Depth;

      int dataPathLength = firstDataPath.Depth;
      int maxValue = secondDataPath.Depth - 1;
      int matchCounter = 0;

      while( matchCounter != dataPathLength )
      {
        if( !firstDataPath[ matchCounter ].Equals( secondDataPath[ matchCounter ] ) )
          return matchCounter - dataPathLength;

        matchCounter++;
      }

      return maxValue - matchCounter;
    }

    private void UpdatePositionningFunction()
    {
      if( this.PositioningFunction != null )
      {
        this.PositioningFunction.UpdateEffectiveViewportSizeInPixels( this.ViewportSizeInPixels );
      }
    }

    #endregion

    #region Recycling

    private void AddContainerToRecyclingWaitingList( ListBoxContainer container )
    {
      Debug.Assert( container != null );
      m_recyclingWaitingList.Add( container );
    }

    private void RemoveContainerFromRecyclingWaitingList( ListBoxContainer container )
    {
      Debug.Assert( container != null );
      m_recyclingWaitingList.Remove( container );
    }

    private void ProcessRecyclingWaitingList()
    {
      List<ListBoxContainer> list = m_recyclingWaitingList.ToList<ListBoxContainer>();

      foreach( ListBoxContainer container in list )
      {
        // Even though we do not explicitly want to defer container recycling
        // the container will stay in the waiting list if it is not elligible
        // for recycling.
        this.RecycleContainerAndLayoutOutOfView( container );
      }
    }

    private void RecycleContainerAndLayoutOutOfView( ListBoxContainer container )
    {
      // When we call this method in such a manner that we want the container recycled
      // as soon as possible, check the container for recycling elligibility.
      // If it does not meet the requirements, then we have no choice but to add it to the
      // waiting list and return.
      bool recycle = container.GetIsElligibleForRecycling();

      if( recycle )
      {
        this.RemoveContainerFromRecyclingWaitingList( container );

        // Remove all animations before clearing any 
        // properties to avoid race conditions
        container.RemoveAllAnimations();

        this.RecycleContainer( container );
        this.ParentListBox.ClearContainerForItem( container );
      }
      else
      {
        this.AddContainerToRecyclingWaitingList( container );
      }

      container.AbsoluteOpacity = PanelBase.DefaultContainerOpacity;
      container.AbsoluteOffset = PanelBase.OutOfViewPoint;
      container.AbsoluteRotationAngle = 0d;
      container.AbsoluteScaleFactor = 1d;
      container.UpdateLayoutTransform();

      if( recycle )
      {
        container.OnRecycledAndLayoutedOutOfView();
      }
    }

    private void RecycleContainer( ListBoxContainer container )
    {
      Queue<ListBoxContainer> recyclingQueue = null;

      ConfigurationType configurationType = container.Configuration.ConfigurationType;

      if( !m_recycleBins.TryGetValue( configurationType, out recyclingQueue ) )
      {
        recyclingQueue = new Queue<ListBoxContainer>();
        m_recycleBins[ configurationType ] = recyclingQueue;
      }

      container.Visibility = System.Windows.Visibility.Collapsed;

      recyclingQueue.Enqueue( container );

      if( this.LayoutManager.DataPathToContainerMapping.ContainsValue( container ) )
      {
        this.LayoutManager.RemoveDataPathToContainerMapping( container.DataPath );
      }
    }

    private ListBoxContainer DequeueRecycledContainer( 
      ListBoxContainerConfigurationBase configuration,
      DataPath dataPath )
    {
      ListBoxContainer container = null;
      ConfigurationType configurationType = configuration.ConfigurationType;

      if( ( dataPath != null ) && ( m_recyclingWaitingList.Count > 0 ) )
      {
        container = ( from item in m_recyclingWaitingList
                      where ( item.Configuration.ConfigurationType == configurationType )
                         && ( object.Equals( item.DataPath, dataPath ) )
                      select item ).FirstOrDefault();

        if( container != null )
        {
          this.RemoveContainerFromRecyclingWaitingList( container );
        }
      }

      if( container == null )
      {
        Queue<ListBoxContainer> recyclingQueue = null;

        if( m_recycleBins.TryGetValue( configurationType, out recyclingQueue ) && ( recyclingQueue.Count > 1 ) )
        {
          container = recyclingQueue.Dequeue();
        }
      }

      if( container != null )
      {
        container.Visibility = System.Windows.Visibility.Visible;
      }

      return container;
    }

    private void ClearRecycleQueues()
    {
      foreach( Queue<ListBoxContainer> recyclingQueue in m_recycleBins.Values )
      {
        recyclingQueue.Clear();
      }

      m_recycleBins.Clear();
      m_recyclingWaitingList.Clear();
    }

    private readonly HashSet<ListBoxContainer> m_recyclingWaitingList = new HashSet<ListBoxContainer>();

    #endregion

    #region LayoutManager Internal Property

    internal PanelLayoutManager LayoutManager
    {
      get;
      private set;
    }

    #endregion

    #region SelectionAndFocusManager Internal Property

    internal PanelSelectionAndFocusManager SelectionAndFocusManager
    {
      get;
      private set;
    }

    #endregion

    #region Internal Methods

    internal virtual bool IsContainerOutOfViewportSupported
    {
      get
      {
        return true;
      }
    }

    internal virtual IOpacityFunction GetOpacityFunction()
    {
      return null;
    }

    internal virtual IScalingFunction GetScalingFunction()
    {
      return null;
    }

    internal virtual IZIndexFunction GetZIndexFunction()
    {
      return null;
    }

    internal virtual double GetConfigurationDistanceUsedForExtent( ListBoxContainerConfigurationBase configuration )
    {
      return configuration.PrepareSize.Height;
    }

    internal virtual void ForceItemOffset( double offset )
    {
      this.ForceVerticalOffset( offset );
    }

    internal static bool ShouldUpdateIScrollInfoFromMeasureOverrideHelper(
      PanelBase panelBase,
      Size availableSize,
      Size currentAvailableSize,
      Orientation orientation )
    {
      bool invalidateScrollInfoRequired = false;

      DataSourceEnumeratorWrapper wrapper = panelBase.ParentListBox.DataSourceEnumeratorWrapper;
      long wrapperIndex = wrapper.Index;

      double newExtent = wrapper.ItemCount;

      if( orientation == Orientation.Vertical )
      {
        if( panelBase.ExtentHeight != newExtent )
        {
          panelBase.ExtentHeight = newExtent;
          invalidateScrollInfoRequired = true;
        }

        if( panelBase.ExtentWidth != availableSize.Width )
        {
          panelBase.ExtentWidth = availableSize.Width;
          invalidateScrollInfoRequired = true;
        }

        invalidateScrollInfoRequired |= ( currentAvailableSize.Height != availableSize.Height );

        // Set the ViewportWidth to the ExtentWidth
        panelBase.ViewportWidth = panelBase.ExtentWidth;

        // Force the VerticalOffset to the DataSourceEnumerator index
        // when the ScrollManager is not active
        if( !panelBase.ScrollManager.IsScrollManagerActive )
        {
          if( Math.Floor( panelBase.VerticalOffset ) != wrapperIndex )
            panelBase.ForceVerticalOffset( wrapperIndex );

          if( ( panelBase.VerticalOffset + panelBase.ViewportHeight ) > newExtent )
            panelBase.SetVerticalOffset( wrapperIndex, false );
        }
      }
      else
      {
        if( panelBase.ExtentWidth != newExtent )
        {
          panelBase.ExtentWidth = newExtent;
          invalidateScrollInfoRequired = true;
        }

        if( panelBase.ExtentHeight != availableSize.Height )
        {
          panelBase.ExtentHeight = availableSize.Height;
          invalidateScrollInfoRequired = true;
        }

        invalidateScrollInfoRequired |= ( currentAvailableSize.Width != availableSize.Width );

        // Set the ViewportHeight to the ExtentHeight
        panelBase.ViewportHeight = panelBase.ExtentHeight;

        // Force the HorizontalOffset to the DataSourceEnumerator index
        // when the ScrollManager is not active
        if( !panelBase.ScrollManager.IsScrollManagerActive )
        {
          if( Math.Floor( panelBase.HorizontalOffset ) != wrapperIndex )
            panelBase.ForceHorizontalOffset( wrapper.Index );

          if( ( panelBase.HorizontalOffset + panelBase.ViewportWidth ) > newExtent )
            panelBase.SetHorizontalOffset( wrapperIndex );
        }
      }

      return invalidateScrollInfoRequired;
    }

    internal void ProcessListBoxContainerKeyDown( ListBoxContainer container, KeyEventArgs e )
    {
      bool handleEvent = false;

      switch( e.Key )
      {
        case Key.Space:
          // Update container selection if current container is focused
          if( this.SelectionAndFocusManager.FindFocusedContainer() == container )
          {
            this.LastNavigationKey = e.Key;
            this.ParentListBox.UpdateSelection( container, SelectionAction.Keyboard );
            handleEvent = true;
          }
          break;

        case Key.End:
          this.LastNavigationKey = e.Key;
          this.HandleKeyEnd();
          handleEvent = true;
          break;

        case Key.Home:
          this.LastNavigationKey = e.Key;
          this.HandleKeyHome();
          handleEvent = true;
          break;

        case Key.PageDown:
          this.LastNavigationKey = e.Key;
          this.HandleKeyPageDown();
          handleEvent = true;
          break;

        case Key.PageUp:
          this.LastNavigationKey = e.Key;
          this.HandleKeyPageUp();
          handleEvent = true;
          break;

        case Key.Down:
          this.LastNavigationKey = e.Key;
          this.HandleKeyDown();
          handleEvent = true;
          break;

        case Key.Up:
          this.LastNavigationKey = e.Key;
          this.HandleKeyUp();
          handleEvent = true;
          break;

        case Key.Left:
          this.LastNavigationKey = e.Key;
          this.HandleKeyLeft();
          handleEvent = true;
          break;

        case Key.Right:
          this.LastNavigationKey = e.Key;
          this.HandleKeyRight();
          handleEvent = true;
          break;

        default:
          break;
      }

      if( handleEvent )
      {
        e.Handled = handleEvent;

        // We must force the InvalidateScrollInfo
        // wil processing a navigation key to ensure
        // the ScrollBar moves during e.g. a Page or Line
        // down that requires a Scroll action because 
        // the target DataPath is of View.
        m_forceInvalidateIScrollInfo = true;
        this.InvalidateScrollInfo();
      }
    }

    internal virtual void ProcessMouseWheel( MouseWheelEventArgs e )
    {
      if( e.Delta < 0 )
      {
        this.MouseWheelDown();
      }
      else
      {
        this.MouseWheelUp();
      }
    }

    internal void ProcessListBoxContainerMouseDown( ListBoxContainer container, MouseButtonEventArgs e, bool bringContainerIntoView )
    {
      if( ( container == null ) || ( container.DataPath == null ) )
        return;

      if( this.SelectionAndFocusManager.HasSelectedDataPath() && this.SelectionAndFocusManager.IsSelectedDataPath( container.DataPath ) )
      {
        this.SelectionAndFocusManager.ClearDataPathToSelectAndFocus();
      }

      if( ( container == LayoutManager.StickyHeader ) || ( !bringContainerIntoView ) )
        return;

      // We cannot bring the container fully into view if the positionning function
      // doesn't support the required functions.
      PositioningFunctionBase positioningFunction = this.PositioningFunction;
      if( ( positioningFunction == null ) || ( !positioningFunction.IsScrollOffsetDifferenceBetweenPositionsSupported ) )
        return;

      bool isScrollingBackward;

      // There is no sticky header to consider.
      if( ( !this.AllowStickyHeaders ) || ( this.LayoutManager.ContainerDescriptions.ForcedStickyContainerDescription == null ) )
      {
        int index = this.LayoutManager.ContainerDescriptions.IndexOf( container.DataPath );

        if( index == -1 )
          return;

        // The container is already fully visible.
        ContainerDescription containerDescription = this.LayoutManager.ContainerDescriptions[ index ];
        if( containerDescription.ProportionInEffectiveViewport >= 1.0d )
          return;

        // We are on the first visible container in the effective viewport if
        // the previous container is not visible at all.
        isScrollingBackward = ( index <= 0 )
                           || ( this.LayoutManager.ContainerDescriptions.Count <= 0 )
                           || ( this.LayoutManager.ContainerDescriptions[ index - 1 ].ProportionInEffectiveViewport <= 0.0d );
      }
      // We must make sure the container isn't partially masked by a sticky header.
      else
      {
        Size stickyHeaderSize = new Size( this.LayoutManager.StickyHeader.AbsoluteElementWidth,
                                          this.LayoutManager.StickyHeader.AbsoluteElementHeight );

        Point containerPosition = container.AbsoluteOffset;
        Size containerSize = new Size( container.ActualWidth, container.ActualHeight );
        Size viewport = this.ViewportSizeInPixels;

        double scrollOffsetForward = this.GetScrollOffsetToBeInBringIntoViewPosition( containerPosition,
                                                                                      containerSize,
                                                                                      stickyHeaderSize,
                                                                                      viewport,
                                                                                      true );
        double scrollOffsetBackward = this.GetScrollOffsetToBeInBringIntoViewPosition( containerPosition,
                                                                                       containerSize,
                                                                                       stickyHeaderSize,
                                                                                       viewport,
                                                                                       false );

        if( !LayoutUtils.AreClose( scrollOffsetForward, scrollOffsetBackward ) )
        {
          // The container is fully visible in the effective viewport if the scroll offsets are
          // of opposite signs.  We still need to check out if the sticky header is over some
          // part of the container.
          if( Math.Abs( Math.Sign( scrollOffsetForward ) - Math.Sign( scrollOffsetBackward ) ) >= 2 )
          {
            // The container isn't under the sticky header.  There is no need to scroll.
            if( Math.Abs( scrollOffsetForward ) + Math.Abs( scrollOffsetBackward ) >= 1.0d )
              return;
          }
        }

        // The container is exactly in the appropriate position.  There is no need to scroll.
        if( LayoutUtils.AreClose( scrollOffsetBackward, 0.0d ) )
          return;

        isScrollingBackward = ( scrollOffsetBackward < 0.0d );
      }

      Key scrollKey = ( isScrollingBackward ) ? Key.Up : Key.Down;
      NavigationInfo navigationInfo = NavigationInfo.GetNavigationInfoFromKey( scrollKey );

      this.LayoutManager.BringContainerCompletlyIntoView( container, navigationInfo );
    }

    internal virtual void OnScrollManagerActualScrollIndexChanged( object sender, EventArgs e )
    {
      this.SetVerticalOffsetCore( this.ScrollManager.ActualScrollIndex );

      this.ScrollManager.UpdateAnimationSettings( this.ExtentHeight - this.ViewportHeight );
    }

    internal void UpdateSelectionPropertiesOnContainers()
    {
      ListBox listBox = this.ParentListBox;

      if( listBox == null )
        return;

      IList<SelectionRange> selectedRanges = listBox.SelectedRanges;
      DataSourceWrapper dataSource = listBox.DataSourceWrapper;

      UIElementCollection children = this.Children;
      int childrenCount = children.Count;

      for( int i = 0; i < childrenCount; i++ )
      {
        ListBoxContainer container = children[ i ] as ListBoxContainer;

        DataPath dataPath = container.DataPath;

        // A null (or not loaded) DataPath means a non prepared container.
        if( ( dataPath == null ) || ( !dataPath.LastChild.IsLoaded ) )
          continue;

        SelectionRange selectedRange = null;

        if( container.IsSelectable )
        {
          selectedRange = SelectionHelper.FindSelectionRangeForPath( dataSource.DataHelper,
            dataPath,
            selectedRanges );
        }

        bool isSelected = ( selectedRange != null )
                       && ( selectedRange.SelectionType == SelectionType.Selection );

        container.SelectionRange = selectedRange;
        container.SetIsSelectedInternal( isSelected );
      }
    }

    internal void InitializeDataSourceEnumerator( DataSourceEnumeratorWrapper dataSourceEnumerator )
    {
      if( this.EnumeratorHelper.DataEnumerator != dataSourceEnumerator )
      {
        this.ForceItemOffset( 0d );
      }

      this.EnumeratorHelper.DataEnumerator = dataSourceEnumerator;
    }

    internal abstract ContainerLayoutMode GetPositioningContainerLayoutMode();

    internal virtual Orientation GetScrollingOrientation()
    {
      return Orientation.Vertical;
    }

    internal void BringItemIntoView( object item )
    {
      DataPath dataPath = item as DataPath;

      if( dataPath == null )
      {
        DataSourceWrapper dataSource = this.ParentListBox.DataSourceWrapper;

        if( dataSource.IsPrimaryKeyVolatile )
          throw new NotSupportedException( "Unable to bring the item into view for the current data source." );

        dataPath = dataSource.CreateDataPathForObject( item );
      }

      if( dataPath == null )
        throw new ListBoxInternalException( "Unable to create DataPath for item for BringItemIntoView." );

      this.JumpToPath( dataPath, null, GroupTarget.Current, false );
    }

    internal DependencyObject GetContainerFromItem( object item )
    {
      DataPath dataPath = item as DataPath;

      if( dataPath == null )
      {
        DataSourceWrapper dataSource = this.ParentListBox.DataSourceWrapper;

        if( dataSource.IsPrimaryKeyVolatile )
          throw new NotSupportedException( "Unable to retrieve the container from the specified item for the current data source." );

        dataPath = dataSource.CreateDataPathForObject( item );
      }

      if( dataPath == null )
        throw new ListBoxInternalException( "Unable to create DataPath for item for GetContainerFromItem." );

      return this.LayoutManager.GetContainerFromPath( dataPath );
    }

    internal virtual AddingItemTransitionAnimation CreateAddingItemTransitionAnimation()
    {
      return new AddingItemTransitionAnimation( this.ParentListBox.AnimationSettings );
    }

    internal virtual RemovingItemTransitionAnimation CreateRemovingItemTransitionAnimation()
    {
      return new RemovingItemTransitionAnimation( this.ParentListBox.AnimationSettings );
    }

    internal virtual TransitionAnimation CreateScrollingUpTransitionAnimation()
    {
      return new ScrollingUpTransitionAnimation( this.ParentListBox.AnimationSettings );
    }

    internal virtual TransitionAnimation CreateScrollingDownTransitionAnimation()
    {
      return new ScrollingDownTransitionAnimation( this.ParentListBox.AnimationSettings );
    }

    internal void AddContainerToPanel( ListBoxContainer container, int zIndex )
    {
      Debug.Assert( !this.Children.Contains( container ) );

      // Simpliest way to do ZIndex
      if( zIndex > 0 )
      {
        this.Children.Add( container );
      }
      else
      {
        this.Children.Insert( 0, container );
      }
    }

    internal void RemoveContainerFromPanel( ListBoxContainer container )
    {
      this.Children.Remove( container );
    }

    internal void ResetPanel()
    {
      // Ensure to clear the DataEnumerator
      this.ParentListBox.ClearDataSourceEnumeratorWrapper();

      // Reset all IScrollInfo properties. Since an InvalidateMeasure is forced,
      // the measure pass will force an InvalidateScrollInfo when the other containers
      // will be set into the PanelBase.
      this.ViewportHeight = 0d;
      this.ViewportWidth = 0d;
      this.VerticalOffset = 0d;
      this.HorizontalOffset = 0d;

      // Only clear the children and the recycling bins
      // when we reset the whole panel, not while refreshing

      // Recycle the sticky header before clearing the panel's childrens
      this.LayoutManager.Reset();

      this.Children.Clear();
      this.ClearRecycleQueues();
      this.SelectionAndFocusManager.ClearDataPathToSelectAndFocus();

      this.Refresh();
    }

    internal void Refresh()
    {
      m_forceRefresh = true;

      // Set the refernece container to null so we compute a new estimated size
      m_referenceContainerForItem = null;
      m_referenceContainerForGroup = null;

      this.InvalidateContainerPreparation();
      this.InvalidateMeasure();
    }

    internal void OnDataEnumerator_DataChanged( object sender, DataChangedEventArgs e )
    {
      // Force the update of the Index and CurrentDataPath of the Enumerator
      // in a Sync DataSource when the underlying source is modified
      // by moving 0 and fetching the actual CurrentDataPath and updating
      // the actual Index of the EnumeratorHelper.
      if( e.Action != DataChangedAction.ItemsLoaded )
      {
        this.EnumeratorHelper.Move( 0 );
      }

      // If the the CurrentDataPath is updated in async source, UpdateViewportContent
      // to make sure the group headers are refreshed properly when group navigation
      // occurs.
      DataPath previouslycurrentDataPath = this.EnumeratorHelper.CurrentDataPath;

      this.EnumeratorHelper.EnsureDataSourceEnumeratorPosition();

      bool isCurrentDataPathUpdated = ( this.EnumeratorHelper.CurrentDataPath == null ) ? previouslycurrentDataPath != null : !this.EnumeratorHelper.CurrentDataPath.Equals( previouslycurrentDataPath );

      TransitionAnimation transition = null;

      if( e.Action == DataChangedAction.Add )
      {
        transition = this.CreateAddingItemTransitionAnimation();
      }
      else if( ( e.Action == DataChangedAction.Remove )
            || ( e.Action == DataChangedAction.Replace )
            || ( e.Action == DataChangedAction.ItemChanged ) )
      {
        DataPath oldDataPath = e.OldItem as DataPath;

        // At the end of the transition, we must recycle and lay out of view
        // the removed container even if it has the focus.
        ListBoxContainer removedContainer = this.LayoutManager.GetContainerFromPath( oldDataPath );

        if( ( removedContainer != null ) && ( FocusHelper.HasFocus( removedContainer ) ) )
        {
          removedContainer.RecycleEvenIfFocused = true;
          // Do not process recycling from the get go since a transition might start for the removed container.
          this.AddContainerToRecyclingWaitingList( removedContainer );
        }

        if( ( oldDataPath != null ) && ( object.Equals( oldDataPath, this.EnumeratorHelper.CurrentDataPath ) ) )
        {
          this.EnumeratorHelper.InvalidateCurrentDataPath();
        }

        if( this.SelectionAndFocusManager.IsSelectedDataPath( oldDataPath ) )
        {
          this.SelectionAndFocusManager.ClearDataPathToSelectAndFocus();
        }

        this.EnumeratorHelper.RefreshCurrentDataPath();

        if( ( e.NewCurrentPosition != SourcePosition.BeforeFirst ) &&
            ( e.NewCurrentPosition != SourcePosition.AfterLast ) )
        {
          transition = this.CreateRemovingItemTransitionAnimation();
        }
        else
        {
          // This can occur when scrolling. Suppose that in an async source
          // we don't have any idea where we are in the data source and we scroll
          // over the first element (or after the last one). We do not want
          // to see the RemovingItemTransitionAnimation. Instead, we want to
          // see a loading animation.
          transition = new LoadingTransitionAnimation( this.ParentListBox.AnimationSettings );
        }
      }
      else if( e.Action == DataChangedAction.ItemsLoaded )
      {
        // If UpdateItems is true, this means a full layout pass is 
        // required to avoid too many ItemsLoaded event
        // Also update viewport content if the currentDataPath has been loaded
        // in async DataSource
        if( e.UpdateItems || ( isCurrentDataPathUpdated && this.ParentListBox.IsAsyncDataSource && !this.LayoutManager.GetHasRunningTransitionAnimations() ) )
        {
          // We must call MoveHelper to ensure the value of CurrentDataPah
          // is correctly updated
          this.LayoutManager.UpdateViewportContent( this.ViewportSizeInPixels );
        }
        else
        {
          this.ProcessItemsReplace( e.LoadedItems );
        }

        this.SelectionAndFocusManager.SelectAndFocusDataPath();
      }
      else if( e.Action == DataChangedAction.Reset )
      {
        this.ResetPanel();
      }

      if( e.NewCurrentPosition != null )
      {
        // Ensure valid Position
        this.EnumeratorHelper.EnsurePositionFromSourcePosition( e.NewCurrentPosition );
      }

      if( transition != null )
      {
        this.LayoutManager.LaunchTransition( transition, this.ViewportSizeInPixels, null );
      }
    }

    internal void JumpToPath( DataPath targetDataPath, 
      NavigationInfo navigationInfo, 
      GroupTarget groupTarget, 
      bool isScrollingSensitive )
    {
      TransitionAnimation transition = this.GetScrollingAnimationFromTargetDataPath( targetDataPath, groupTarget );
      bool selectTargetContainer = false;

      if( navigationInfo == null )
      {
        // For a simple bring into view we don't need to update de selection
        // nor the focus so we simulate a dummy navigation key just
        // to jump to the DataPath
        navigationInfo = NavigationInfo.GetNavigationInfoFromKey( Key.A );
        navigationInfo.ForcedBringIntoView = true;
      }
      else
      {
        // Register to the CompletedEvent so we can set the focus state on the container
        transition.Completed += new EventHandler<TransitionAnimationEventArgs>( this.OnScrollingAnimationCompleted );
        selectTargetContainer = true;
      }

      // Find out the operation to do on the source to get the target dataPath.
      long moveCount = 0;

      switch( navigationInfo.Key )
      {
        case Key.Down:
          //If on a row or collapsed group, move to following item.
          moveCount = 1;
          break;

        case Key.Up:
          //If on a row or collapsed group.
          moveCount = -1;
          break;

        case Key.PageDown:
          //Make sure we won't jump with expectedMoveCount at 0.
          moveCount = ( int )Math.Max( this.ItemsInViewport, 1d );
          break;

        case Key.PageUp:
          //Make sure we won't jump with expectedMoveCount at 0.
          moveCount = -( int )Math.Max( this.ItemsInViewport, 1d );
          break;

        default:
          break;
      }


      Size viewport = this.ViewportSizeInPixels;
      DataPath effectiveDataPath = targetDataPath;

      // Get on the target dataPath in the source.
      if( this.EnumeratorHelper.JumpMoveAndFetch( targetDataPath, moveCount ) )
      {
        effectiveDataPath = this.EnumeratorHelper.CurrentDataPath;
        double itemOffset = this.EnumeratorHelper.CurrentIndex;

        // We'll use the positioning function that gives us the position of a
        // bring into view container only if we are able to calculate the scroll
        // offset and if the starting position isn't forced be the context.
        PositioningFunctionBase positioningFunction = this.PositioningFunction;
        bool useBringIntoViewPosition = ( isScrollingSensitive ) && 
                                        ( positioningFunction != null ) &&
                                        ( positioningFunction.IsScrollOffsetDifferenceBetweenPositionsSupported );

        if( useBringIntoViewPosition )
        {
          // Find out the scrolling direction to get to the target DataPath.
          DataPath destinationDataPath = ( effectiveDataPath.LastChild.IsLoaded ) ? effectiveDataPath : targetDataPath;
          bool isTargetDataPathForward = ( this.GetTargetDataPathDirectionFromCurrentPosition( destinationDataPath, groupTarget ).GetValueOrDefault() > 0 );

          // Get the sticky header size if there's a sticky header
          Size stickyHeaderSize = this.LayoutManager.GetStickyHeaderSize();



          Size itemContainerSize = this.ParentListBox.GetContainerFactory( ConfigurationType.Item ).PrepareSize;

          positioningFunction.UpdateEffectiveViewportSizeInPixels( viewport );
          positioningFunction.PrepareLayoutPass( 0.0d, itemContainerSize, stickyHeaderSize );

          Point origin;
          double containerProportionInViewport;
          int layoutProgressionPercentage;
          double rotateAngle;

          // Find out the position of the first item that will be layouted.
          positioningFunction.PlaceContainerForward( itemContainerSize,
                                                     false,
                                                     out origin,
                                                     out containerProportionInViewport,
                                                     out layoutProgressionPercentage,
                                                     out rotateAngle );

          // Find out the distance between the first layouted item and the target container.
          double targetOffset = this.GetScrollOffsetToBeInBringIntoViewPosition( origin,
                                                                                 itemContainerSize,
                                                                                 stickyHeaderSize,
                                                                                 viewport,
                                                                                 isTargetDataPathForward );

          // Don't apply any offset if it isn't meaningful.
          if( !LayoutUtils.AreClose( targetOffset, 0.0d ) )
          {
            long moveOffset = ( long )Math.Ceiling( Math.Abs( targetOffset ) ) * Math.Sign( targetOffset );

            // We want to move on the first item of the resulting layout.  We must to a particular
            // move on the source to make sure the effective data path is tracked.  We need to do
            // this if we wish to receive the "ItemsLoaded" event from the enumerator for the
            // effective data path.  Without the "ItemsLoaded" event, we will not be able to
            // set the focus on the container on the final layout.  This, of course, only happens
            // when dummies are retrieve from the enumerator.
            this.EnumeratorHelper.Move( moveOffset, true );

            // Find out the partial offset to apply on the first layouted item to make the
            // target container fully visible.
            itemOffset = this.EnumeratorHelper.CurrentIndex + targetOffset - moveOffset;
          }
        }

        this.ForceItemOffset( itemOffset );
      }

      if( selectTargetContainer )
      {
        // Set the data path of the container we want the focus on once loaded.
        this.SelectionAndFocusManager.SetSelectedDataPath( effectiveDataPath );
      }

      this.LayoutManager.LaunchTransition( transition, viewport, null );
    }

#if WINDOWS_PHONE
    internal void InitializeManipulation()
    {
      m_manipulationStartScrollOffset = ( this.GetScrollingOrientation() == Orientation.Vertical ) ? this.VerticalOffset : this.HorizontalOffset;

      // If there is an animation running, stop it by setting current value as target value
      if( this.ScrollManager.IsScrollManagerActive )
        this.ScrollManager.ScrollToIndex( m_manipulationStartScrollOffset, 0, null, null );
    }

    internal void ApplyManipulationDelta( Point delta, Point cumulative, double containerHeight, double containerWidth )
    {
      double scrollTarget = ( this.GetScrollingOrientation() == Orientation.Vertical ) ?
        m_manipulationStartScrollOffset - ( cumulative.Y / containerHeight ) :
        m_manipulationStartScrollOffset - ( cumulative.X / containerWidth );

      this.ScrollManager.ScrollToIndex( scrollTarget, 0, null, null );
    }

    internal void CompleteManipulation( bool isInertial, ManipulationDelta totalDelta, ManipulationVelocities finalVelocities, double containerHeight, double containerWidth )
    {
      if( isInertial )
      {
        PowerEase ease = new PowerEase();
        ease.EasingMode = EasingMode.EaseOut;
        ease.Power = 3;

        // Get initial velocity (in pixels / miliseconds)
        double initialVelocity = ( ( this.GetScrollingOrientation() == Orientation.Vertical ) ?
          finalVelocities.LinearVelocity.Y : finalVelocities.LinearVelocity.X ) / 1000;

        // Get animation duration in miliseconds based on standard inertia deceleration
        // Duration is equal to initial velocity (in pixels / miliseconds) divided by deceleration (in pixels / miliseconds squared)
        double duration = Math.Abs( initialVelocity ) / InertiaDeceleration;

        // Initial velocity is the difference in distance within a very short amount of time at the start of the animation.
        // Using u as a short amount of time to use initial velocity where u = 1/1000 msec
        // Using Tdist the total distance to animate (in pixels), ease the easing function 
        // and duration the total length of the animation (in miliseconds)
        // This means the initial velocity (in pixels / miliseconds) is equal to ( Tdist * ease( u / duration ) ) / u
        double u = 1d / 1000d; //msec
        double distance = ( initialVelocity * u ) / ( ease.Ease( u / duration ) );

        double scrollTarget = ( this.GetScrollingOrientation() == Orientation.Vertical ) ?
          m_manipulationStartScrollOffset - ( ( totalDelta.Translation.Y + distance ) / containerHeight ) :
          m_manipulationStartScrollOffset - ( ( totalDelta.Translation.X + distance ) / containerWidth );


        this.ScrollManager.ScrollToIndex( scrollTarget, Convert.ToInt32( duration ), ease, null );
      }
    }
#endif

    #endregion

    #region Private Methods

    private void Initialize( Size viewportSize )
    {
      this.IsInitialized = true;
      this.LayoutManager.LaunchLoadingTransition( viewportSize );
    }

    private void UpdateIScrollInfoOffsetFromDataSourceEnumeratorIndex( long index )
    {
      if( this.ScrollManager.IsScrollManagerActive )
        return;

      double newIndex = index + ( this.ItemOffset % 1 );

      this.ForceItemOffset( newIndex );
    }

    private void OnScrollManagerScrollCompleted( object sender, ScrollCompletedEventArgs e )
    {
      m_forceInvalidateIScrollInfo = false;
      this.InvalidateScrollInfo();
    }

    private void OnEnumeratorMoveCompleted( object sender, MoveCompletedEventArgs e )
    {
      this.UpdateIScrollInfoOffsetFromDataSourceEnumeratorIndex( e.Index );
    }

    private void ProcessItemsReplace( IEnumerable<Tuple<object, object>> loadedItems )
    {
      foreach( Tuple<object, object> loadedItem in loadedItems )
      {
        this.ProcessItemReplace( loadedItem.Item1 as DataPath, loadedItem.Item2 as DataPath );
      }
    }

    private void ProcessItemReplace( DataPath oldDataPath, DataPath newDataPath )
    {
      // Update the CurrentDataPath if it is replaced
      if( ( this.EnumeratorHelper.CurrentDataPath != null )
          && ( this.EnumeratorHelper.CurrentDataPath.Depth == oldDataPath.Depth )
          && ( this.EnumeratorHelper.CurrentDataPath.LastChild == oldDataPath.LastChild ) )
      {
        this.EnumeratorHelper.CurrentDataPath = newDataPath;
      }

      ListBoxContainer stickyHeader = this.LayoutManager.StickyHeader;

      // Update the sticky header's DataPath if it is replaced
      if( ( stickyHeader != null )
          && ( stickyHeader.DataPath.Depth == oldDataPath.Depth )
          && ( stickyHeader.DataPath.LastChild == oldDataPath.LastChild ) )
      {
        DataPath GroupDataPath = newDataPath.CreateAncestorPath( Math.Max( 1, newDataPath.Depth - 1 ) );
        this.ParentListBox.PrepareContainerForItem( stickyHeader, GroupDataPath );
      }

      // Update the DataPath of the container to focus on if it is replaced.
      if( this.SelectionAndFocusManager.IsSelectedDataPath( oldDataPath ) )
      {
        this.SelectionAndFocusManager.SetSelectedDataPath( newDataPath );
      }

      // Try to find a DataPath that has the same LastChild in the DataPathToContainerMapping
      // in order to update it.
      ListBoxContainer container = null;
      DataPath oldMappedDataPath = null;

      foreach( KeyValuePair<DataPath, ListBoxContainer> keyValuePair in this.LayoutManager.DataPathToContainerMapping )
      {
        DataPath mappedDataPath = keyValuePair.Key;

        if( ( mappedDataPath.Depth == oldDataPath.Depth ) && ( mappedDataPath.LastChild == oldDataPath.LastChild ) )
        {
          oldMappedDataPath = mappedDataPath;
          container = keyValuePair.Value;
          break;
        }
      }

      // No DataPath matching the oldDataPath was found (mainly group DataPaths)
      // ignore it. The real DataPath loaded will be received later.
      if( oldMappedDataPath == null )
        return;

      Debug.Assert( container != null, "A container was not mapped to a DataPath in DataPathToContainerMapping." );

      // Update the container description when a replace occurs
      ContainerDescription containerDescription = this.LayoutManager.ContainerDescriptions[ oldMappedDataPath ];

      if( containerDescription != null )
        containerDescription.DataPath = newDataPath;

      // While processing the ItemReplace, we need to be careful and watch 
      // the focused container. We need to do a special processing if we replace a 
      // DataPath equals to the focused container DataPath to not lose the focused 
      // container (and produce a leak). If a replaced DataPath equals the focused 
      // container DataPath we DO NOT remplace the dummy DataPath with the newDataPath. 
      // We need to remove the container associate to the dummy dataPath from the 
      // DataPathToContainerMapping, apply the geometric transformations of the dummy 
      // container to the focused container, add the focused container in 
      // the DataPathToContainerMapping and finally recycle the unused dummy container.

      ListBoxContainer focusedContainer = this.SelectionAndFocusManager.FindFocusedContainer();
      DataPath focusedDataPath = null;
      bool replacingFocusedContainer = false;

      if( focusedContainer != null )
      {
        focusedDataPath = focusedContainer.DataPath;
      }

      if( focusedDataPath != null )
      {
        if( focusedDataPath.Equals( newDataPath ) )
          replacingFocusedContainer = true;
      }

      if( !replacingFocusedContainer )
      {
        // Remplace the dummy dataPath with the new DataPath
        if( this.LayoutManager.RemoveDataPathToContainerMapping( oldMappedDataPath ) )
        {
          this.ParentListBox.PrepareContainerForItem( container, newDataPath );
          this.LayoutManager.AddDataPathToContainerMapping( newDataPath, container );
        }
      }
      else
      {
        // Remove the  dummy data path from the mapping 
        this.LayoutManager.RemoveDataPathToContainerMapping( oldMappedDataPath );

        // Apply the transformations of the dummy container to 
        // the focused container so we have a fluid movement of the container
        focusedContainer.AbsoluteOpacity = container.AbsoluteOpacity;
        focusedContainer.AbsoluteScaleFactor = container.AbsoluteScaleFactor;
        focusedContainer.AbsoluteRotationAngle = container.AbsoluteRotationAngle;
        focusedContainer.MaximumHeight = container.MaximumHeight;
        focusedContainer.MaximumWidth = container.MaximumWidth;
        focusedContainer.AbsoluteOffset = container.AbsoluteOffset;
        focusedContainer.ZOrder = container.ZOrder;

        // Update the size and the position of the focused container
        focusedContainer.UpdateLayoutTransform();
        focusedContainer.UpdateSize();

        // Add the focused container to the DataPathToContainerMapping
        // because it's obviously in view (we are in the item remplace method)

        this.LayoutManager.AddDataPathToContainerMapping( newDataPath, focusedContainer );

        this.RemoveContainerFromRecyclingWaitingList( focusedContainer );

        // Recycle the unused container that was associated to the 
        // Dummy DataPath


        // Even though we do not explicitly want to defer container recycling
        // the container will be put in the waiting list if it is not elligible
        // for recycling.
        this.RecycleContainerAndLayoutOutOfView( container );
      }
    }


    private ContainerDescription PrepareStickyHeaderDescription( ContainerDescriptionList containerDescriptions )
    {
      if( this.ParentListBox.GroupDescriptions.Count() == 0 )
        return null;

      if( containerDescriptions == null )
        return null;

      ListBox listBox = this.ParentListBox;

      // Get first group in the effective viewport
      ContainerDescription firstContainerInViewport = this.GetFirstContainerDescriptionOfTheEffectiveViewport( containerDescriptions );

      if( firstContainerInViewport == null )
        return null;

      DataPath itemDataPath = firstContainerInViewport.DataPath;
      DataPath firstGroupDataPath = itemDataPath.CreateAncestorPath( Math.Max( 1, itemDataPath.Depth - 1 ) );

      // Get position of last element of this group in the viewport
      ContainerDescription lastContainerDescriptionForGroup = this.GetLastContainerDescriptionForAGroup( firstGroupDataPath, containerDescriptions );
      ListBoxContainerConfigurationBase itemConfiguration = listBox.GetContainerFactory( ConfigurationType.Item );
      ListBoxContainerConfigurationBase groupConfiguration = listBox.GetContainerFactory( ConfigurationType.GroupHeader );

      // Create and fill the sticky header container description
      ContainerDescription stickyDescription = new ContainerDescription();
      double angle = 0.0d;
      stickyDescription.TargetPosition =
        this.PositioningFunction.PlaceStickyHeader( lastContainerDescriptionForGroup.TargetPosition, groupConfiguration.PrepareSize, out angle );
      stickyDescription.ZIndex = ZIndexFunction.MaxZIndex;
      stickyDescription.Configuration = groupConfiguration;
      if( itemDataPath.LastChild.IsLoaded )
      {
        stickyDescription.DataPath = firstGroupDataPath;
      }
      else
      {
        stickyDescription.DataPath = itemDataPath;
      }
      stickyDescription.RotateAngle = angle;
      stickyDescription.ScalingFactor = 1.0;
      stickyDescription.OpacityFactor = 1.0;
      stickyDescription.ProportionInEffectiveViewport = 1.0;

      containerDescriptions.ForcedStickyContainerDescription = stickyDescription;
      return stickyDescription;
    }

    // This method looks for the last container of a specified group in the effective viewport.
    // This method assume that the container descriptions (the list) is ordered from the first container
    // in the viewport to the last one.
    private ContainerDescription GetLastContainerDescriptionForAGroup( DataPath groupDataPath, ContainerDescriptionList containerDescriptions )
    {
      DataPath containerDataPath = null;
      DataPath groupContainerDataPath = null;
      ContainerDescription lastContainerForAGroup = null;
      foreach( ContainerDescription containerDescription in containerDescriptions )
      {
        // If the container isn't in the viewport, go to the next one
        if( containerDescription.ProportionInEffectiveViewport <= 0.0d )
          continue;

        containerDataPath = containerDescription.DataPath;
        groupContainerDataPath = containerDataPath.CreateAncestorPath( Math.Max( 1, containerDataPath.Depth - 1 ) );

        if( groupContainerDataPath.Equals( groupDataPath ) )
        {
          lastContainerForAGroup = containerDescription;
        }
      }

      return lastContainerForAGroup;
    }

    // This method looks for the first container of the effective viewport.
    // This method assume that the container descriptions (the list) is ordered from the first container
    // in the viewport to the last one.
    private ContainerDescription GetFirstContainerDescriptionOfTheEffectiveViewport( ContainerDescriptionList containerDescriptions )
    {
      foreach( ContainerDescription containerDescription in containerDescriptions )
      {
        if( containerDescription.DataPath.LastChild is GroupDataItem )
          continue;

        if( containerDescription.ProportionInEffectiveViewport > 0.0d )
          return containerDescription;
      }

      return null;
    }

    // This methode looks if there is a group break between
    // the previous DataPath and the current DataPath.
    private bool IsCurrentDataPathAGroupBreak()
    {
      bool hasGroupDescriptions = this.ParentListBox.GroupDescriptions.Count > 0;

      if( !hasGroupDescriptions )
        return false;

      DataPath currentDataPath = this.EnumeratorHelper.CurrentDataPath;
      bool isFirstSourceItem = false;
      bool areGroupDifferent = false;

      if( this.EnumeratorHelper.Move( -1 ) )
      {
        areGroupDifferent = IsDataPathAGroupBreak( this.EnumeratorHelper.CurrentDataPath, currentDataPath );
      }
      else
      {
        // There is no previous dataPath, we are at the beginning of the source.
        isFirstSourceItem = true;
      }

      // Replace the enumerator
      this.EnumeratorHelper.Move( 1 );

      return areGroupDifferent || isFirstSourceItem;
    }

    private bool IsDataPathAGroupBreak( DataPath previousDataPath, DataPath currentDataPath )
    {
      // If there is no currentDataPath, the can be no group break
      if( currentDataPath == null )
        return false;

      bool hasGroupDescriptions = this.ParentListBox.GroupDescriptions.Count > 0;

      if( !hasGroupDescriptions )
        return false;

      // If there are group descriptions but no previous DataPath, it is a group break
      if( previousDataPath == null )
        return true;

      return ( PanelBase.CompareDataPaths( previousDataPath, currentDataPath ) < -1 );
    }

    private double GetScrollOffsetToBeInBringIntoViewPosition( 
      Point originalPosition,
      Size itemContainerSize, 
      Size groupHeaderContainerSize, 
      Size viewportSizeInPixels,
      bool forwardScrolling )
    {
      Point bringIntoViewPosition =
        this.PositioningFunction.GetBringIntoViewPositionForContainer( groupHeaderContainerSize,
                                                                       itemContainerSize,
                                                                       viewportSizeInPixels,
                                                                       forwardScrolling );
      double scrollOffset =
        this.PositioningFunction.GetScrollOffsetDifferenceBetweenPositions( originalPosition,
                                                                            bringIntoViewPosition,
                                                                            itemContainerSize );

      if( LayoutUtils.AreClose( scrollOffset, 0.0d ) )
        return 0.0d;

      return scrollOffset;
    }


    #endregion

    #region KeyboardNavigation Methods

    private void HandleKeyUp()
    {
      NavigationInfo navigationInfo = NavigationInfo.GetNavigationInfoFromKey( Key.Up );
      this.MoveUpDownLeftRight( navigationInfo );
    }

    private void HandleKeyDown()
    {
      NavigationInfo navigationInfo = NavigationInfo.GetNavigationInfoFromKey( Key.Down );
      this.MoveUpDownLeftRight( navigationInfo );
    }

    private void HandleKeyLeft()
    {
      NavigationInfo navigationInfo = NavigationInfo.GetNavigationInfoFromKey( Key.Left );
      this.MoveUpDownLeftRight( navigationInfo );
    }

    private void HandleKeyRight()
    {
      NavigationInfo navigationInfo = NavigationInfo.GetNavigationInfoFromKey( Key.Right );
      this.MoveUpDownLeftRight( navigationInfo );
    }

    private void HandleKeyHome()
    {
      NavigationInfo navigationInfo = NavigationInfo.GetNavigationInfoFromKey( Key.Home );
      this.MoveEndOrHome( navigationInfo );
    }

    private void HandleKeyEnd()
    {
      NavigationInfo navigationInfo = NavigationInfo.GetNavigationInfoFromKey( Key.End );
      this.MoveEndOrHome( navigationInfo );
    }

    private void HandleKeyPageUp()
    {
      NavigationInfo navigationInfo = NavigationInfo.GetNavigationInfoFromKey( Key.PageUp );
      this.MoveUpDownLeftRight( navigationInfo );
    }

    private void HandleKeyPageDown()
    {
      NavigationInfo navigationInfo = NavigationInfo.GetNavigationInfoFromKey( Key.PageDown );
      this.MoveUpDownLeftRight( navigationInfo );
    }



    private DataPath GetDataPathAccordingToNavigationInfo( DataPath currentPath,
      NavigationInfo navigationInfo )
    {
      ListBoxContainer currentContainer = this.LayoutManager.GetContainerFromPath( currentPath );

      // Ensure the container is at least partially visible
      // before returning the first or last fully visible
      // container in the viewport. If not, we want to bring
      // into view the current container and process navigation
      // from this container.
      if( this.LayoutManager.IsContainerOutOfViewport( currentContainer, true ) )
        return null;

      if( navigationInfo.IsPageScrolling )
      {
        return this.LayoutManager.GetFirstOrLastFullyVisibleDataPathForCurrentDataPath( currentPath,
           navigationInfo );
      }
      else
      {
        return this.LayoutManager.GetNextOrPreviousDataPathForCurrentDataPath( currentPath,
         navigationInfo );
      }
    }

    private void OnNavigationScrollManagerScrollCompleted( object sender, ScrollCompletedEventArgs e )
    {
      m_scrollManagerCompletedRegistered = false;
      this.ScrollManager.ScrollCompleted -= this.OnNavigationScrollManagerScrollCompleted;

      // If selection has not changed during transition, Focus or Select Container
      DataPath focusedDataPath = this.SelectionAndFocusManager.GetFocusedOrCurrentDataPath();
      if( ( ( m_scrollStartSelectedDataPath != null ) && ( m_scrollStartSelectedDataPath.Equals( focusedDataPath ) ) ) ||
        ( ( m_scrollStartSelectedDataPath == null ) && ( focusedDataPath == null ) ) )
      {
        this.SelectionAndFocusManager.BeginFocusOrSelectContainerAfterScrollForcedForNavigation( ( e.NavigationAction != null ) ? e.NavigationAction.NavigationInfo : null );
      }

      m_scrollStartSelectedDataPath = null;
    }

    private void MoveUpDownLeftRight( NavigationInfo navigationInfo )
    {
      // We must have a current or focused DataPath in order to start
      // keyboard navigation

      DataPath currentOrFocusedPath = this.SelectionAndFocusManager.GetFocusedOrCurrentDataPath();

      if( currentOrFocusedPath == null )
        return;

      ListBox listBox = this.ParentListBox;
      double scrollManagerTargetIndex = this.ScrollManager.TargetIndex;

      ListBoxContainer currentOrFocusedPathContainer = this.LayoutManager.GetContainerFromPath( currentOrFocusedPath, true );

      bool isCurrentNavigationActionBringIntoView = ( this.ScrollManager.ActiveNavigationAction != null ) && 
        ( this.ScrollManager.ActiveNavigationAction is BringContainerCompletelyIntoViewNavigationAction );

      // No container realized for the current DataPath or the ScrollManager is currently active, except for a BringIntoView
      if( ( currentOrFocusedPathContainer == null ) ||
        ( ( this.ScrollManager.IsScrollManagerActive ) && !isCurrentNavigationActionBringIntoView ) )
      {
        this.ProcessUpDownLeftRightForNonRealizedContainerOrWhileScrolling( scrollManagerTargetIndex, navigationInfo );
        return;
      }

      if( isCurrentNavigationActionBringIntoView )
      {
        BringContainerCompletelyIntoViewNavigationAction bringIntoViewNavigationAction = this.ScrollManager.ActiveNavigationAction as BringContainerCompletelyIntoViewNavigationAction;
        currentOrFocusedPath = bringIntoViewNavigationAction.TargetDataPath;
        currentOrFocusedPathContainer = this.LayoutManager.GetContainerFromPath( currentOrFocusedPath, true );
      }

      if( ( currentOrFocusedPathContainer != null )
          && ( listBox.SelectionMode != SelectionMode.Multiple ) )
      {
        // Ensure to reset the Selected ans Focused states
        // of the previously current container in case scrolling
        // is required. This will prevent the old Selected and Focused
        // container to keep this state while it changes during a 
        // scrolling animation.
        if( !navigationInfo.IsCtrlDown )
          currentOrFocusedPathContainer.SetIsSelectedInternal( false );

        currentOrFocusedPathContainer.IsVisuallyFocused = false;
      }

      bool selectAndFocusOrScrollToDataPathDone =
        this.SelectAndFocusOrScrollToDataPathAccordingToNavigationInfo( currentOrFocusedPath, navigationInfo );

      if( selectAndFocusOrScrollToDataPathDone )
        return;

      // This will force a Scroll operation and all the process
      // to Select and Focus will be done in the OnNavigationScrollManagerScrollCompleted
      // callback
      this.ForceScrollToBringContainerIntoViewAndProcessNavigation( currentOrFocusedPathContainer,
        navigationInfo );
    }

    private void ForceScrollToBringContainerIntoViewAndProcessNavigation(
      ListBoxContainer currentOrFocusedPathContainer,
      NavigationInfo navigationInfo )
    {
      // If the currentOrFocusedDataPath is not null, we need to jump to this
      // DataPath and move according to navigation
      if( ( currentOrFocusedPathContainer != null )
        && ( ( currentOrFocusedPathContainer.Translation.Y == PanelBase.OutOfViewOffset )
           || ( currentOrFocusedPathContainer.Translation.X == PanelBase.OutOfViewOffset ) ) )
      {
        navigationInfo.ForcedBringIntoView = true;
        this.JumpToPath( currentOrFocusedPathContainer.DataPath, navigationInfo, GroupTarget.Current, true );
      }
      else
      {
        double scrollManagerIndex = this.ScrollManager.TargetIndex;
        ScrollToIndexNavigationAction navigationAction = new ScrollToIndexNavigationAction( scrollManagerIndex, this, navigationInfo );
        double targetDataPathScrollIndex = navigationAction.CalculateTargetScrollIndex();

        // Ensure to focus or select the currentOrFocusedPathContainer and
        // affect the IsSelected and IsVisuallyFocused property that
        // were reset before the Move* operation since no GotFocus
        // will be received for this container neither any selection change
        if( scrollManagerIndex == targetDataPathScrollIndex )
        {
          this.SelectionAndFocusManager.FocusOrSelectContainerAfterScrollForcedForNavigation( navigationInfo );

          if( ( currentOrFocusedPathContainer != null )
            && ( this.ParentListBox.SelectionMode != SelectionMode.Multiple ) )
          {
            // Ensure to reset the Selected ans Focused states
            // of the previously current container in case scrolling
            // is required. This will prevent the old Selected and Focused
            // container to keep this state while it changes during a 
            // scrolling animation.
            if( !navigationInfo.IsCtrlDown )
              currentOrFocusedPathContainer.SetIsSelectedInternal( true );

            currentOrFocusedPathContainer.IsVisuallyFocused = true;
          }
        }
        else
        {
          this.StartScrollingAnimation( targetDataPathScrollIndex, navigationInfo.IsPageScrolling, navigationAction );
        }
      }
    }

    private TransitionAnimation GetScrollingAnimationFromTargetDataPath( DataPath targetDataPath, GroupTarget groupTarget )
    {
      int compare = this.GetTargetDataPathDirectionFromCurrentPosition( targetDataPath, groupTarget ).GetValueOrDefault();

      if( compare < 0 )
      {
        return this.CreateScrollingUpTransitionAnimation();
      }
      else if( compare > 0 )
      {
        return this.CreateScrollingDownTransitionAnimation();
      }
      else
      {
        return new LoadingTransitionAnimation( this.ParentListBox.AnimationSettings );
      }
    }

    private Nullable<int> GetTargetDataPathDirectionFromCurrentPosition( DataPath targetDataPath, GroupTarget groupTarget )
    {
      // We don't have a clue where is located the target dataPath.
      if( targetDataPath == null )
        return null;

      // If the target dataPath isn't loaded, we use the GroupTarget to find the 
      // scrolling animation.
      if( !targetDataPath.LastChild.IsLoaded )
      {
        if( groupTarget == GroupTarget.Previous )
        {
          return -1;
        }
        else if( groupTarget == GroupTarget.Next )
        {
          return 1;
        }

        // We don't know where is located the target dataPath.
        return null;
      }

      // Compare the two DataPath to figure out the scrolling direction

      DataPath originalDataPath = this.LayoutManager.GetFirstFullyVisibleDataPath();
      if( originalDataPath == null )
      {
        originalDataPath = this.EnumeratorHelper.CurrentDataPath;
      }

      if( !originalDataPath.LastChild.IsLoaded )
        return null;

      return this.ParentListBox.DataSourceWrapper.DataHelper.Compare( targetDataPath, originalDataPath );
    }

    private void OnScrollingAnimationCompleted( object sender, TransitionAnimationEventArgs e )
    {
      TransitionAnimation transitionAnimation = sender as TransitionAnimation;
      transitionAnimation.Completed -= this.OnScrollingAnimationCompleted;

      this.SelectionAndFocusManager.BeginSelectAndFocusDataPath();
    }

    private bool SelectAndFocusOrScrollToDataPathAccordingToNavigationInfo( DataPath currentOrFocusedPath, NavigationInfo navigationInfo )
    {
      bool isShiftDown = ( Keyboard.Modifiers & ModifierKeys.Shift ) == ModifierKeys.Shift;
      bool isCtrlDown = navigationInfo.IsCtrlDown;

      // Try to directly move to the desired DataPath. It will be null
      // if the currentOrFocusedPath is out of viewport to ensure
      // a bring into view will be queued before the selection is processed.
      DataPath targetDataPath = this.GetDataPathAccordingToNavigationInfo( currentOrFocusedPath,
        navigationInfo );

      ListBoxContainer targetContainer = null;

      if( ( targetDataPath != null ) && targetDataPath.LastChild.IsLoaded )
      {
        this.SelectionAndFocusManager.ClearDataPathToSelectAndFocus();

        this.ParentListBox.CurrentDataPath = targetDataPath;

        targetContainer = this.LayoutManager.GetContainerFromPath( targetDataPath, true );

        if( targetContainer != null )
        {
          // Here, we can be sure that the container is in the realized items.
          // So what we want to do is move this container at the top of the layout by scrolling
          // to the right target index of the scroll manager.
          if( this.LayoutManager.IsContainerOutOfViewport( targetContainer ) )
          {
            if( this.LayoutManager.BringContainerCompletlyIntoView( targetContainer, navigationInfo ) )
              return true;

            targetContainer = null;
          }

          //The container was found and is fully
          //into view, nothing to do
          if( targetContainer != null )
          {
            if( !isCtrlDown || isShiftDown )
              this.ParentListBox.UpdateSelection( targetContainer, SelectionAction.Navigation );

            targetContainer.Focus();
            this.ProcessRecyclingWaitingList();
            return true;
          }
        }
      }

      return false;
    }

    private void ProcessUpDownLeftRightForNonRealizedContainerOrWhileScrolling( double scrollManagerTargetIndex, NavigationInfo navigationInfo )
    {
      // Use the TargetIndex at which the ScrollManager is positionned to calculate the next index to scroll to
      // since it can be currently active.
      ScrollToIndexNavigationAction navigationAction = new ScrollToIndexNavigationAction( scrollManagerTargetIndex, this, navigationInfo );
      double newTargetScrollIndex = navigationAction.CalculateTargetScrollIndex();
      bool isForwardScrolling = navigationInfo.IsForwardScrolling;

      if( ( isForwardScrolling && ( newTargetScrollIndex > scrollManagerTargetIndex ) )
          || ( !isForwardScrolling && ( newTargetScrollIndex < scrollManagerTargetIndex ) ) )
      {
        this.StartScrollingAnimation( newTargetScrollIndex, navigationInfo.IsPageScrolling, navigationAction );
      }
      else
      {
        // Only update the state of the ScrollManager in case the scrolling behavior
        // was not modified

        if( this.ScrollManager.ActiveNavigationAction != null )
        {
          this.ScrollManager.ActiveNavigationAction.NavigationInfo = navigationInfo;
        }
      }

    }

    private void MoveEndOrHome( NavigationInfo navigationInfo )
    {
      MoveEndOrHomeNavigationAction navigationAction = new MoveEndOrHomeNavigationAction( this, navigationInfo );
      double targetIndex = navigationAction.CalculateTargetScrollIndex();

      double scrollManagerTargetIndex = this.ScrollManager.TargetIndex;
      bool scrollRequired = navigationInfo.IsForwardScrolling ? ( scrollManagerTargetIndex < targetIndex ) : ( scrollManagerTargetIndex != 0 );

      if( scrollRequired )
      {
        this.StartScrollingAnimation( targetIndex, navigationInfo.IsPageScrolling, navigationAction );
      }
      else
      {
        this.SelectionAndFocusManager.ClearDataPathToSelectAndFocus();

        DataPath navigatedDatapath = null;

        if( navigationInfo.IsForwardScrolling )
        {
          navigatedDatapath = this.LayoutManager.GetLastFullyVisibleDataPath();
        }
        else
        {
          navigatedDatapath = this.LayoutManager.GetFirstFullyVisibleDataPath();
        }

        // If there are not fully visible DataPath in the viewport,
        // do not attempt to get a container for it
        if( navigatedDatapath == null )
          return;

        ListBoxContainer container = this.LayoutManager.GetContainerFromPath( navigatedDatapath );
        bool isCtrlDown = navigationInfo.IsCtrlDown;

        if( !isCtrlDown || ( isCtrlDown && ( ( Keyboard.Modifiers & ModifierKeys.Shift ) == ModifierKeys.Shift ) ) )
        {
          this.ParentListBox.UpdateSelection( container, SelectionAction.Navigation );
        }

        if( container != null )
        {
          container.Focus();
          this.ProcessRecyclingWaitingList();
        }
      }
    }

    private void StartScrollingAnimation( double targetIndex, bool isPageScrolling, PanelNavigationActionBase navigationAction )
    {
      ListBox listBox = this.ParentListBox;
      int duration = 0;
      IEasingFunction easingFunction = null;

      if( isPageScrolling )
      {
        duration = listBox.AnimationSettings.GetPageScrollingDuration();
        easingFunction = listBox.AnimationSettings.GetPageScrollingEasingFunction();
      }
      else
      {
        duration = listBox.AnimationSettings.GetLineScrollingDuration();
        easingFunction = listBox.AnimationSettings.GetLineScrollingEasingFunction();
      }

      //Subscribe to the ScrollCompleted event so we can set the current element appropiately in the event handler
      if( !m_scrollManagerCompletedRegistered )
      {
        m_scrollStartSelectedDataPath = this.SelectionAndFocusManager.GetFocusedOrCurrentDataPath();
        this.ScrollManager.ScrollCompleted += this.OnNavigationScrollManagerScrollCompleted;
        m_scrollManagerCompletedRegistered = true;
      }

      this.ScrollManager.ScrollToIndex( targetIndex, duration, easingFunction, navigationAction );
    }

    private double GetGroupHeaderProportion()
    {
      if( this.PositioningFunction != null )
      {
        double headerProportion = this.PositioningFunction.GetGroupHeaderProportion();
        return headerProportion;
      }

      return 0.0d;
    }

    #endregion

    #region Private Fields

    private static double LineSize = 1;
#if !WINDOWS_PHONE
    private static double WheelSize = SystemParameters.WheelScrollLines * StackPanel.LineSize;
#else
    // Constant multiplier because parameter does no exist in WP7
    private static double WheelSize = 3 * StackPanel.LineSize;
#endif
    private static Point EmptyPoint = new Point( 0d, 0d );
    private static double OutOfViewOffset = -9999d;
    private static Point OutOfViewPoint = new Point( -9999d, -9999d );
    private static double DefaultContainerOpacity = 0.0d;

    private Size m_viewportSizeInPixels = Size.Empty;
    private Size m_extent;
    private Size m_viewport;
    private Point m_offset;
    private ScrollViewer m_scrollOwner;
    private Size m_actualContainerSize = new Size( 0d, 0d );
    private int m_iSupportInitializeCount = 0;
    private double m_itemsInViewport = 0.0d;

    private bool m_forceInvalidateIScrollInfo;

    private bool m_scrollManagerCompletedRegistered;
    private PositioningFunctionBase m_positioningFunction = null;

    private Dictionary<object, Queue<ListBoxContainer>> m_recycleBins =
      new Dictionary<object, Queue<ListBoxContainer>>();


    private bool m_forceRefresh;
    private ListBoxContainer m_referenceContainerForItem = null;
    private ListBoxContainer m_referenceContainerForGroup = null;

    private DataPath m_scrollStartSelectedDataPath = null;

#if WINDOWS_PHONE
    private double m_manipulationStartScrollOffset = 0.0d;
#endif

    #endregion

    #region IWeakEventListener Members

    bool IWeakEventListener.ReceiveWeakEvent( Type managerType, object sender, EventArgs e )
    {
      bool handled = false;

      if( managerType == typeof( LostMouseCaptureManager ) )
      {
        this.UpdateOffsets();

        handled = true;
      }

      return handled;
    }

    #endregion

    #region IScrollInfo implementation

    internal bool ForceInvalidateScrollInfo
    {
      get
      {
        return m_forceInvalidateIScrollInfo;
      }
    }

    internal void InvalidateScrollInfo()
    {
      ScrollViewer scrollViewer = this.ScrollOwner;

      if( scrollViewer != null )
      {
        scrollViewer.InvalidateScrollInfo();
      }
    }

    internal bool CanHorizontallyScroll
    {
      get;
      set;
    }

    internal bool CanVerticallyScroll
    {
      get;
      set;
    }

    internal double ExtentHeight
    {
      get
      {
        return m_extent.Height;
      }
      set
      {
        m_extent.Height = value;
      }
    }

    internal double ExtentWidth
    {
      get
      {
        return m_extent.Width;
      }
      set
      {
        m_extent.Width = value;
      }
    }

    internal double HorizontalOffset
    {
      get
      {
        return m_offset.X;
      }
      set
      {
        m_offset.X = value;
      }
    }

    internal ScrollViewer ScrollOwner
    {
      get
      {
        return m_scrollOwner;
      }
      set
      {
        if( value != m_scrollOwner )
        {
          if( m_scrollOwner != null )
          {
            LostMouseCaptureManager.RemoveListener( m_scrollOwner, this );
          }

          m_scrollOwner = value;

          if( m_scrollOwner != null )
          {
            LostMouseCaptureManager.AddListener( m_scrollOwner, this );
          }
        }
      }
    }

    internal double VerticalOffset
    {
      get
      {
        return m_offset.Y;
      }
      set
      {
        m_offset.Y = value;
      }
    }

    internal double ViewportHeight
    {
      get
      {
        return m_viewport.Height;
      }
      set
      {
        if( m_viewport.Height != value )
        {
          m_viewport.Height = value;
        }
      }
    }

    internal double ViewportWidth
    {
      get
      {
        return m_viewport.Width;
      }
      set
      {
        m_viewport.Width = value;
      }
    }

    internal void LineDown()
    {
      this.SetOffset( PanelBase.LineSize, true, true );
    }

    internal void LineLeft()
    {
      this.SetOffset( PanelBase.LineSize, false, false );
    }

    internal void LineRight()
    {
      this.SetOffset( PanelBase.LineSize, true, false );
    }

    internal void LineUp()
    {
      this.SetOffset( PanelBase.LineSize, false, true );
    }

#if SILVERLIGHT
    internal Rect MakeVisible( UIElement visual, Rect rectangle )
    {
      throw new NotImplementedException();
    }
#else
    internal Rect MakeVisible( Visual visual, Rect rectangle )
    {
      // Bringing the items into view is already handled at mouse down.  To prevent the behavior
      // being different in WPF and Silverlight, this method does nothing.
      return rectangle;
    }
#endif

    internal void MouseWheelDown()
    {
      this.SetOffset( PanelBase.WheelSize, true, true );
    }

    internal void MouseWheelLeft()
    {
      this.SetOffset( PanelBase.WheelSize, false, false );
    }

    internal void MouseWheelRight()
    {
      this.SetOffset( PanelBase.WheelSize, true, false );
    }

    internal void MouseWheelUp()
    {
      this.SetOffset( PanelBase.WheelSize, false, true );
    }

    internal void PageDown()
    {
      this.SetOffset( m_itemsInViewport, true, true );
    }

    internal void PageLeft()
    {
      this.SetOffset( m_itemsInViewport, false, false );
    }

    internal void PageRight()
    {
      this.SetOffset( m_itemsInViewport, true, false );
    }

    internal void PageUp()
    {
      this.SetOffset( m_itemsInViewport, false, true );
    }

    internal void SetOffset( double jumpSize, bool jumpForward, bool verticalOffset )
    {
      double scrollOffset = jumpSize;

      // if there is a sticky header, we need to correct the 
      // jump so we do not scroll for more than an item
      if( ( this.LayoutManager.StickyHeader != null ) && ( this.IsCurrentDataPathAGroupBreak() ) )
      {
        if( jumpForward )
        {
          scrollOffset -= this.GetGroupHeaderProportion();
        }
        else
        {
          scrollOffset += this.GetGroupHeaderProportion();
        }
      }

      if( !jumpForward )
      {
        scrollOffset *= -1.0d;
      }

      if( verticalOffset )
      {
        this.SetVerticalOffset( this.ScrollManager.TargetIndex + scrollOffset, true );
      }
      else
      {
        this.SetHorizontalOffset( this.ScrollManager.TargetIndex + scrollOffset, true );
      }

    }

    internal void SetHorizontalOffset( 
      double offset, 
      bool fromIScrollInfoLineOrPageOperation = false,
      bool animate = true )
    {
      if( this.IsDeferredScrollingEnabled( fromIScrollInfoLineOrPageOperation, false ) )
      {
        this.SetDeferredOffset( offset, false );
      }
      else
      {
        m_forceInvalidateIScrollInfo = fromIScrollInfoLineOrPageOperation;

        // Correct the offset to ensure it is in a valid range for IScrollInfo
        offset = Math.Min( this.ExtentWidth - this.ViewportWidth, offset );
        offset = Math.Max( 0, offset );

        if( this.HorizontalOffset == offset )
          return;

        // To ensure scrolled to item is fully visible
        offset = this.ScrollManager.RoundIndexToClosestItem( offset );

        int duration = 0;
        IEasingFunction easingFunction = null;

        if( animate )
        {
          ListBoxAnimationSettings animationSettings = this.ParentListBox.AnimationSettings;

          duration = animationSettings.GetThumbScrollingDuration();
          easingFunction = animationSettings.GetThumbScrollingEasingFunction();
        }

        this.ScrollManager.ScrollToIndex( offset,
          duration,
          easingFunction,
          null );
      }
    }

    internal virtual void SetHorizontalOffsetCore( double offset )
    {
      this.SetVerticalOffsetCore( offset );
    }

    internal virtual void ForceHorizontalOffset( double offset )
    {
      this.ScrollManager.ForceScrollIndex( this.HorizontalOffset, offset );
      this.HorizontalOffset = offset;
      this.InvalidateScrollInfo();
    }

    internal void SetVerticalOffset( 
      double offset, 
      bool fromIScrollInfoLineOrPageOperation = false,
      bool animate = true )
    {
      if( this.IsDeferredScrollingEnabled( fromIScrollInfoLineOrPageOperation, true ) )
      {
        this.SetDeferredOffset( offset, true );
      }
      else
      {
        m_forceInvalidateIScrollInfo = fromIScrollInfoLineOrPageOperation;

        // Correct the offset to ensure it is in a valid range for IScrollInfo
        offset = Math.Min( this.ExtentHeight - this.ViewportHeight, offset );
        offset = Math.Max( 0, offset );

        if( this.VerticalOffset == offset )
          return;

        // To ensure scrolled to item is fully visible
        offset = this.ScrollManager.RoundIndexToClosestItem( offset );

        int duration = 0;
        IEasingFunction easingFunction = null;

        if( animate )
        {
          ListBoxAnimationSettings animationSettings = this.ParentListBox.AnimationSettings;

          duration = animationSettings.GetThumbScrollingDuration();
          easingFunction = animationSettings.GetThumbScrollingEasingFunction();
        }

        this.ScrollManager.ScrollToIndex( offset,
          duration,
          easingFunction,
          null );
      }
    }

    internal virtual void SetVerticalOffsetCore( double offset )
    {
      // Correct the offset to ensure it is in a valid range for IScrollInfo
      offset = Math.Min( offset, this.ExtentHeight - this.ViewportHeight );
      offset = Math.Max( 0, offset );

      if( offset != this.VerticalOffset )
      {
        long newValue = ( long )offset;
        long oldValue = ( long )this.VerticalOffset;
        long moveDifference = newValue - oldValue;

        if( moveDifference != 0 )
          this.EnumeratorHelper.Move( moveDifference );

        this.VerticalOffset = offset;

        if( this.ForceInvalidateScrollInfo )
        {
          this.InvalidateScrollInfo();
        }

        this.LayoutManager.UpdateViewportContent( this.RenderSize );
      }
    }

    internal virtual void ForceVerticalOffset( double offset )
    {
      this.ScrollManager.ForceScrollIndex( this.VerticalOffset, offset );
      this.VerticalOffset = this.ScrollManager.TargetIndex;
      this.InvalidateScrollInfo();
    }

    #endregion

    #region IScrollInfo Members

    bool IScrollInfo.CanHorizontallyScroll
    {
      get
      {
        return this.CanHorizontallyScroll;
      }
      set
      {
        this.CanHorizontallyScroll = value;
      }
    }

    bool IScrollInfo.CanVerticallyScroll
    {
      get
      {
        return this.CanVerticallyScroll;
      }
      set
      {
        this.CanVerticallyScroll = value;
      }
    }

    double IScrollInfo.ExtentHeight
    {
      get
      {
        return this.ExtentHeight;
      }
    }

    double IScrollInfo.ExtentWidth
    {
      get
      {
        return this.ExtentWidth;
      }
    }

    double IScrollInfo.HorizontalOffset
    {
      get
      {
        return this.HorizontalOffset;
      }
    }

    void IScrollInfo.LineDown()
    {
      this.LineDown();
    }

    void IScrollInfo.LineLeft()
    {
      this.LineLeft();
    }

    void IScrollInfo.LineRight()
    {
      this.LineRight();
    }

    void IScrollInfo.LineUp()
    {
      this.LineUp();
    }

#if SILVERLIGHT
    Rect IScrollInfo.MakeVisible( UIElement visual, Rect rectangle )
    {
      return this.MakeVisible( visual, rectangle );
    }
#else
    Rect IScrollInfo.MakeVisible( Visual visual, Rect rectangle )
    {
      return this.MakeVisible( visual, rectangle );
    }
#endif

    void IScrollInfo.MouseWheelDown()
    {
      this.MouseWheelDown();
    }

    void IScrollInfo.MouseWheelLeft()
    {
      this.MouseWheelLeft();
    }

    void IScrollInfo.MouseWheelRight()
    {
      this.MouseWheelRight();
    }

    void IScrollInfo.MouseWheelUp()
    {
      this.MouseWheelUp();
    }

    void IScrollInfo.PageDown()
    {
      this.PageDown();
    }

    void IScrollInfo.PageLeft()
    {
      this.PageLeft();
    }

    void IScrollInfo.PageRight()
    {
      this.PageRight();
    }

    void IScrollInfo.PageUp()
    {
      this.PageUp();
    }

    ScrollViewer IScrollInfo.ScrollOwner
    {
      get
      {
        return this.ScrollOwner;
      }
      set
      {
        this.ScrollOwner = value;
      }
    }

    void IScrollInfo.SetHorizontalOffset( double offset )
    {
      this.SetScrollInfoOffset( offset, false );
    }

    void IScrollInfo.SetVerticalOffset( double offset )
    {
      this.SetScrollInfoOffset( offset, true );
    }

    double IScrollInfo.VerticalOffset
    {
      get
      {
        return this.VerticalOffset;
      }
    }

    double IScrollInfo.ViewportHeight
    {
      get
      {
        return this.ViewportHeight;
      }
    }

    double IScrollInfo.ViewportWidth
    {
      get
      {
        return this.ViewportWidth;
      }
    }

    #endregion

    #region ISupportInitialize Members

    internal bool IsInitializing
    {
      get
      {
        return ( m_iSupportInitializeCount > 0 );
      }
    }


    void ISupportInitialize.BeginInit()
    {
      m_iSupportInitializeCount++;

      if( m_iSupportInitializeCount == 1 )
      {
        this.BeginInitCore();
      }
    }

    void ISupportInitialize.EndInit()
    {
      if( m_iSupportInitializeCount == 0 )
        return;

      m_iSupportInitializeCount--;

      if( m_iSupportInitializeCount == 0 )
      {
        this.EndInitCore();
      }
    }

    internal virtual void BeginInitCore()
    {
    }

    internal virtual void EndInitCore()
    {
    }

    #endregion
  }
}
